/*
 * Copyright Aristool AG Switzerland
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.opprua.tidy.beans;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.opprua.tidy.util.TimePoint;

/**
 * TODO: add Java doc
 * 
 * Prepared for first commit: 15.07.2007
 * 
 * @author Lin Li
 */
public class BeanSorter {

  private enum ELEMENT_COMPARE {
    VALUE, DATE_TIME, CASE_INSENEITIVE_STRING
  };

  public static abstract class Ranker {

    private boolean ascending;

    public Ranker(boolean ascending) {
      this.ascending = ascending;
    }

    public abstract Comparable<? extends Object> rank(Object o);
  }

  private static class UnitRanker extends Ranker {

    public UnitRanker(boolean ascending) {
      super(ascending);
    }

    @SuppressWarnings("unchecked")
    public Comparable<? extends Object> rank(Object o) {
      return (Comparable<? extends Object>) o;
    }
  }

  private static class SortBy extends Ranker {

    private String attr;
    private ELEMENT_COMPARE value;

    public SortBy(String attributeName, ELEMENT_COMPARE sortValue,
                boolean ascending) {
      super(ascending);
      attr = attributeName;
      value = sortValue;
    }

    public String getAttr() {
      return attr;
    }

    public ELEMENT_COMPARE getValue() {
      return value;
    }

    @SuppressWarnings("unchecked")
    public Comparable<? extends Object> rank(Object o) {
      Comparable<? extends Object> c = null;
      if (attr != null) {
        c = (Comparable<? extends Object>) BeanHandler.get(o, getAttr());
      }
      if (c == null) {
        throw new Error("Can not sort on null value: " + attr);
      }

      if (getValue() == ELEMENT_COMPARE.DATE_TIME) {
        return TimePoint.from(c).getTime();
      }

      if (getValue() == ELEMENT_COMPARE.CASE_INSENEITIVE_STRING) {
        return c.toString().toUpperCase();
      }
      return c;
    }
  }

  @SuppressWarnings("unchecked")
  private static class Elem implements Comparable<Elem> {

    Object data = null;
    Comparable<? extends Object>[] values = null;
    Ranker[] rankers = null;

    public Elem(Object original, Ranker[] rankers) {
      data = original;
      this.rankers = rankers;
      values = new Comparable[rankers.length];
      for (int i = 0; i < rankers.length; i++) {
        values[i] = rankers[i].rank(original);
      }
    }

    public int compareTo(Elem e) {

      int res = 0;
      int item = 0;
      while (item < rankers.length && res == 0) {
        res = ((Comparable) values[item])
                    .compareTo((Comparable) e.values[item]);
        item++;
      }
      item--;
      if (item > -1 && !rankers[item].ascending) {
        res *= -1;
      }
      return res;
    }
  }

  private List<Ranker> rankers = new ArrayList<Ranker>();

  public BeanSorter() {
    super();
  }

  public BeanSorter addRanker(Ranker r) {
    rankers.add(r);
    return this;
  }

  public BeanSorter addSelfRanker(boolean ascending) {
    rankers.add(new UnitRanker(ascending));
    return this;
  }

  public BeanSorter addSortField(String fieldName, ELEMENT_COMPARE sortType,
              boolean ascending) {
    return addRanker(new SortBy(fieldName, sortType, ascending));
  }

  public void sort(Object[] d) {
    if (d == null || d.length == 0) {
      return;
    }

    Ranker[] r = (Ranker[]) rankers.toArray(new Ranker[rankers.size()]);
    Elem[] data = new Elem[d.length];
    for (int i = 0; i < data.length; i++) {
      data[i] = new Elem(d[i], r);
    }

    Arrays.sort(data);

    for (int i = 0; i < data.length; i++) {
      d[i] = data[i].data;
    }
  }

  public static void sort(Object[] data, String fieldName,
              ELEMENT_COMPARE sortSpec, boolean ascending) {
    if (data == null || data.length == 0) {
      return;
    }
    if (fieldName == null || fieldName.length() == 0) {
      return;
    }
    new BeanSorter().addSortField(fieldName, sortSpec, ascending).sort(data);
  }
}